package classificator.tree;

import classificator.data.Data;
import classificator.data.Attribute;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**Modella l'astrazione dell'entità nodo di split (continuo o discreto) estendendo la superclasse Node. 
 * Al suo interno incapsula la classe SplitInfo che aggrega tutte le informazioni 
 * riguardanti un nodo di Split. 
 *
 * @author 467644
 */
public abstract class SplitNode extends Node implements Comparable<SplitNode>{
    //Oggetto di tipo Attribute che modella  l'attributo indipendente sul quale lo split è generato 
    private Attribute attribute; 
    //Array per memorizzare gli split candidati in una struttura dati di dimensione 
    //pari ai possibili valori di test 
    protected List<SplitInfo> mapSplit = new ArrayList<SplitInfo> ();
    //Attributo che contiene il valore di information gain per lo split corrente 
    protected float infoGain; 
    
    /**Invoca il costruttore della superclasse, 
     * ordina i valori dell'attributo di input per gli esempi beginExampleIndex-endExampleIndex 
     * sfrutta questo ordinamento per determinare i possibili split e popolare l'array mapSplit[], 
     * computa l'entropia per l'attributo usato nello split e determina il 
     * corrispondente information gain. 
     * 
     * @param trainingSet training set complessivo
     * @param beginExampleIndex indice iniziale del sotto-insieme di training
     * @param endExampleIndex indice finale del sotto-insieme di training
     * @param attribute attributo indipendente sul quale si definisce lo split 
     */
    SplitNode(Data trainingSet, int beginExampleIndex, int endExampleIndex, Attribute attribute){
        super(trainingSet, beginExampleIndex, endExampleIndex);
        this.attribute = attribute;
        // order by attribute
        trainingSet.sort(attribute, beginExampleIndex, endExampleIndex); 
        setSplitInfo(trainingSet, beginExampleIndex, endExampleIndex, attribute);					
        //compute entropy=sum_i{pi*E(i)} i=1..m ;m = number of classes
        float splitEntropy = 0;
        int divisore, dividendo;
        for(int i=0; i<mapSplit.size(); i++){
            SplitInfo infosplit = mapSplit.get(i);
            dividendo = infosplit.getEndIndex() - infosplit.getBeginindex()+1;
            divisore = endExampleIndex - beginExampleIndex + 1;
            float p = (float) dividendo / divisore;
            //System.out.println(i+"-"+mapSplit.size()+"-"+mapSplit.get(i).getBeginindex()+"-"+mapSplit.get(i).getEndIndex());
            LeafNode leaf = new LeafNode(trainingSet, infosplit.getBeginindex(),
                                                infosplit.getEndIndex());
            float localEntropy = leaf.getEntropy();
            splitEntropy += p*localEntropy;
        }
        //compute info gain
        infoGain = entropy - splitEntropy;
    }

    /**metodo abstract per generare le informazioni necessarie per ciascuno degli split 
     * candidati (in mapSplit[])
     * 
     * @param trainingSet training set complessivo
     * @param beginExampleIndex indice iniziale del sotto-insieme di training
     * @param endExampleIndex indice finale del sotto-insieme di training
     * @param attribute attributo indipendente sul quale si definisce lo split 
     */
    abstract void setSplitInfo(Data trainingSet,int beginExampelIndex, int endExampleIndex, Attribute attribute); 

    /**metodo abstract per modellare la condizione di test 
     * (per ogni valore di test c'è un ramo dallo split) 
     * 
     * @param value valore dell'attributo che si vuole testare rispetto a tuttti gli split 
     * @return 
     */
    abstract int testCondition (Object value); 

    /**Restituisce l'attributo usato per lo split 
     * 
     * @return attributo
     */
    Attribute getAttribute() {
        return attribute;
    }

    /**restituisce l'information gain per lo split corrente 
     * 
     * @return information gain
     */
    float getInformationGain() {
        return infoGain;
    }

    /**restituisce il numero dei rami originanti nel nodo corrente
     * 
     * @return numero rami
     */
    @Override
    public int getNumberOfChildren() {
        return mapSplit.size();
    }

    /**restituisce le informazioni per il ramo in mapSplit[] indicizzato da child. 
     * 
     * @param child figlio
     * @return informazioni ramo
     */
    SplitInfo getSplitInfo(int child){
        return mapSplit.get(child);
    }
    
    /**concatena le informazioni di ciascun test (attributo, operatore e  valore) 
     * 
     * 
     * @return stringa contenente informazioni sullo split corrente 
     * (domanda da fare all'utente per decidere quale ramo di split seguire) 
     */
    public String formulateQuery(){
        String query = "";
        for(int i=0; i<mapSplit.size(); i++)
            query += i + ":" + attribute.getName() + mapSplit.get(i).getComparator() + 
                    mapSplit.get(i).getSplitValue() + "\n";
        return query;
    }

    /**concatena le informazioni di ciascun test (attributo, esempi coperti, information gain ed entropia)
     * 
     * @return 
     */
    @Override
    public String toString(){
        String v = "SPLIT : attribute=" + attribute + " " + super.toString() + 
                "Info Gain: " + getInformationGain()+ "\n" ;
        for(int i=0; i<mapSplit.size(); i++)
                v += "\t" + mapSplit.get(i) + "\n";
        return v;
    }
    
/**Confrontare i valori di information gain dei due nodi e restituire l'esito
 * 
 * @param splitnode Nodo di split da confrontare con il corrente nodo DiscreteNode
 * @return Esito del confronto (0:uguali, -1 gain minore, 1 gain maggiore)
 */
    @Override
    public int compareTo(SplitNode splitnode){
        if (this.getInformationGain()==splitnode.getInformationGain())
            return 0;
        else if (this.getInformationGain() < splitnode.getInformationGain())
            return 1;
        else if (this.getInformationGain() > splitnode.getInformationGain())
            return -1;
        return 0;
    }
            
    /**Aggrega tutte le informazioni riguardanti un nodo di split
     * 
     */
    class SplitInfo implements Serializable{
        //Valore di tipo Object (di un attributo indipendente) che definisce uno split 
        Object splitValue;  
        //Numero di split (nodi figli) originanti dal nodo corrente 
        int numberChild;
        //indici estremi del sotto-insieme di training
        int beginIndex, endIndex;
        //Operatore matematico che definisce il test nel nodo corrente (“=” per valori discreti) 
        String comparator = "=";
        
        /**Costruttore che avvalora gli attributi di classe per split a valori discreti 
         * 
         * @param splitValue
         * @param beginIndex
         * @param endIndex
         * @param numberChild 
         */
        SplitInfo(Object splitValue, int beginIndex, int endIndex, int numberChild){ 
            this.splitValue = splitValue;
            this.numberChild = numberChild;
            this.beginIndex=beginIndex;
            this.endIndex=endIndex;
        }
                
        /**Costruttore che avvalora gli attributi di classe per generici split 
         * (da usare per valori continui) 
         * 
         * @param splitValue
         * @param beginIndex
         * @param endIndex
         * @param numberChild
         * @param comparator 
         */
        SplitInfo(Object splitValue, int beginIndex, int endIndex, int numberChild, String comparator){
            this.splitValue = splitValue;
            this.numberChild = numberChild;
            this.comparator = comparator;
            this.beginIndex=beginIndex;
            this.endIndex=endIndex;
        }
        
        /**Restituisce il valore dello split 
         * 
         * @return valore split
         */
        Object getSplitValue(){
            return splitValue;
        }
        
        /**restituisce l'indice iniziale
         * 
         * @return indice iniziale
         */
        int getBeginindex(){
            return beginIndex;			
        }
        
        /**restituisce l'indice finale
         * 
         * @return indice finale
         */
        int getEndIndex(){
            return endIndex;
        }

        /**Concatena in un oggetto String i valori di beginExampleIndex, 
         * endExampleIndex, child, splitValue, comparator e restituisce la stringa finale. 
         * 
         * @return stringa concatenata
         */
        @Override
        public String toString() {
            return "child " + numberChild + " split value" + comparator + splitValue + 
                    "[Examples:" + beginIndex + "-" + endIndex + "]";
        }

        /**Restituisce il valore dell'operatore matematico che definisce il test 
         * 
         * @return operatore matematico
         */
        String getComparator() {
            return comparator;
        }
    }
}